avatar

目录
322 - Different Type Members

Different Type Members

🧑‍💻 User

In this lecture, we’re going to explore the situation when we are setting up a class, but it has membervariables of other types.So it’s basically going to be different from what we had in the last lecture where we had member variablesof the same class, why we are trying to declare the same class.So here is a class we’re going to use.It’s called Integer.It’s basically going to be a wrapper on an int type and it’s going to have a bunch of the constructors.We have the destructor here and we have a getter and setter that we can use to manipulate the valuethat we are wrapping around here.Nothing complicated here, but we’re going to be setting up member variables of integer type in ourpoint class, and here we’re going to basically try and simulate what we had in the last lecture.Here we have a member variable of integer type.Our integer class, we have a cast member variable.You’re going to see that this is going to work.We have an inline static variable.We can set this up.It’s going to work.We can initialize that here because it is inline.You already know this.We can have a const static member variable of integer type and we can initialize that in here.This is also going to work.You see that the limitations we had in the last lecture don’t apply because the member variable is basicallydifferent from the class that we are trying to set up here.I want you to be aware of this.Another point which is going to be very different is if we try to use a pointer member variable likewe are doing here, we initialize this to null pointer, but if we hit the implementation, you’re goingto see that now we don’t have to go through a function to set up new memory and allocate that on theheap.We can do this directly in the initializer list and this is going to work because we’re not trying tocall the constructor multiple times.It is going to be a pointer constructor calling an integer constructor.So we’re not going to have that chain of the constructor calling itself like we had in the last lecture.And this is going to work just fine.We’re not going to crash our program.If we go down, we see that we can create a static integer member variable.This is going to work.We can even make that const, this is going to work.But because this static member variables are not in line, we’re going to have to initialize them inthe CPP file.You have to remember that.So if we go in our CPP file, we’re going to see that we are initializing IE6 and IE7 and putting valuesinside.And this is really all if the member variable is not of the same class that you are declaring, youare free to use many things as you want and it is going to work.Now that you know this, we’re going to head over to Visual Studio code and play with this a littlemore.Okay.Here we are in our working folder.The current project is member variables of other types.We are going to grab our template files and use them here, so we’re going to put them in place.Okay, here we are in our working folder.The current project is member variables of other types.We’re going to grab our template files and we’re going to put them in place.Let’s do that and we’re going to open this in Visual Studio code by dragging and dropping here, thisis going to open our project and we’re going to create the classes that we want to use.We want to use the point class and the integer class.So let’s create the integer class first.We’re going to say integer, dot h for the header and we’re going to say integer dot cpp for the implementationfile.I am going to grab the code for integer, dot h and put that in.Here.We have our include guard, we have the class declaration inside, we have our member variable whichis inner and we are basically wrapping around this and through this integer class, which is our owncustom class here.We are also going to put in the implementation for the class and in the implementation file we’re goingto have our constructor and destructor.This is nothing you don’t know yet.We’re going to just close this class here because it is already defined and we’re going to create ourpoint class.We’re going to create a point dot h class header file, I should say, and we’re going to do the implementationfile, which is going to be point dot CPP and we are going to put in the header file.Let’s do that.And again, it is nothing you haven’t seen before.We have our include guard inside.We have the declaration for the class point.We have our good old member variables X and y.We have the point count static variable.But what we really want to see are these member variables of integer type.We can use all this.Taxes here.And this is going to work.Unlike what we had in the last lecture where the member variables were of point type.Basically creating incomplete type compiler errors if we use syntaxes like this here.But in this case it is working because the member variable type is different from the class type here.Okay.This is then we’re going to put in the implementation part and here it is basically what you alreadyknow.The main difference is that now, because integer six and seven are static and they are not inline,we will have to initialize them in the CPP file and that’s what we do here.Another main difference for you to see is that if we use a pointer, we can initialize this directlyas null pointer.This is going to work, but if we go in the CPP file now we have the option to initialize this directlywithout going through some member function like we did in the last lecture.The reason is, again, we don’t have a situation in which the constructor for Point is going to callitself repeatedly.The types are different.So here Point is going to call the constructor for Integer and this is going to work.We’re not going to have any problem.Okay.Now that we have this setup, we can head over in the main CPP file.We can clean it up a little bit and remove what we don’t need.We’re going to include our point class.Let’s do that.We’re going to include that and we can go in the main function and do all kinds of crazy things.For example, we’re going to put in a piece of code which is going to try the point class a little bit.We’re going to set up a point object.We can print the distance.We can get the point that we have in here.For example, we can access I1 i2 i3 I4 If we try to set the value for I1 and I2, we’re going to geta compiler error.If we try to set up I1.This is going to work because I1 is not const.If we go in we’re going to see that it is not const here, but if we go in main and try to change Ito let’s go back I2 is cost.If we try to change that, we’re going to get a compiler error.Let’s try to use this.We’re going to see that we’re going to get a squiggly line.This is going to give us a compiler error.Let’s build and you’re going to see that, okay, we have a compiler error and we are trying to calland we’re going to get our compiler error build finished with errors.And the problem is that we are trying to modify something that is cost.So we get to this error here that we are trying to use this pointer to modify something that is costand this is not going to be allowed.Okay, So this is the compiler error.We can go down and access the point five.And you remember that this was a pointer, but we’re going to get the value inside and use that thisis going to work.We can access point six and seven and we’re going to get all these values here.This is going to work just fine if we try to set the value for I six.Let’s go back and see what six is.It is a non const integer.So this is going to work.But if we try to do the same for I seven, we’re going to get a compiler error because I seven is constand we can see that here.Okay.So once we do all these things, we’re going to end our test and things are going to work exactly likewe expect.Let’s build again.We shouldn’t get any compiler error now.The build is good.We can clear and run Rooster and we’re going to get our data in our output terminal.This is really all I wanted you to see in this lecture that you can set up things like this.Please try to go through this and really make sense of what you see here.You can even try and set up your own variables and really try and break things.That’s how we learn.We are going to stop here in this lecture and the next one we’re going to try and learn about staticmember functions.Go ahead and finish up here and meet me there.


🤖 Assistant

好的,同学们,欢迎来到我们的C++进阶课程!

标题:探索C++类中“其他类型”成员变量的奥秘

概要

本节课我们将深入学习C++类中如何设置非自身类型的成员变量(member variables)以及它们与“自身类型”成员变量的不同之处。我们将通过一个Integer封装类和一个Point类来具体演示各种成员变量的声明、定义和初始化方法,重点分析为什么这些场景下的限制比上一讲中“自身类型”成员变量的限制要少,并提供详细的代码示例、常见误区和练习。


2. 详细讲解:C++类中的非自身类型成员变量

同学们好!在上一讲中,我们探讨了C++类如何拥有其自身类型的成员变量,并发现了一些限制,比如不能直接声明一个非指针的自身类型成员变量,以及在构造函数中初始化时的递归问题。今天,我们将把目光投向一个更常见、也更灵活的场景:当一个类拥有其他类型(other types)的成员变量时。

为了更好地理解这个概念,我们将创建一个简单的Integer类,它将作为对C++内置int类型的一个封装(wrapper)。这意味着Integer类的对象将包含一个int类型的值,并提供一些方法来操作它。然后,我们将在Point类中使用这个Integer类作为其成员变量。

2.1 Integer 类:一个简单的数值封装器

首先,我们来定义我们的Integer类。这个类非常简单,它包含一个私有的int成员变量,以及一些构造函数(constructors)、析构函数(destructor)、获取器(getter)和设置器(setter)。

Integer.h 文件:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ifndef INTEGER_H
#define INTEGER_H

#include <iostream> // 包含iostream用于输出

class Integer {
private:
int m_value; // 封装的int值

public:
// 默认构造函数
Integer() : m_value(0) {
std::cout << "Integer::Integer() - Default constructor for " << m_value << std::endl;
}

// 带参构造函数
Integer(int value) : m_value(value) {
std::cout << "Integer::Integer(int) - Parameterized constructor for " << m_value << std::endl;
}

// 拷贝构造函数
Integer(const Integer& other) : m_value(other.m_value) {
std::cout << "Integer::Integer(const Integer&) - Copy constructor for " << m_value << std::endl;
}

// 析构函数
~Integer() {
std::cout << "Integer::~Integer() - Destructor for " << m_value << std::endl;
}

// 获取值
int getValue() const {
return m_value;
}

// 设置值
void setValue(int value) {
m_value = value;
}
};

#endif // INTEGER_H

Integer.cpp 文件:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 实际上,Integer类的方法如果都很短,也可以直接在头文件中实现
// 但为了保持良好的实践,我们通常会把方法的定义放在.cpp文件
// 不过对于简单的getter/setter和构造函数,直接在头文件内联实现是很常见的
// 这里为了简化,我们假设它们在头文件内联实现,所以Integer.cpp可以为空
// 或者我们也可以像下面这样显式定义:
/*
#include "Integer.h"
#include <iostream>

// 这里省略了构造函数和析构函数的定义,因为它们已经在头文件里写了内联实现
// 如果不在头文件内联实现,需要在这里定义:
// Integer::Integer() : m_value(0) {
// std::cout << "Integer::Integer() - Default constructor for " << m_value << std::endl;
// }
// Integer::Integer(int value) : m_value(value) {
// std::cout << "Integer::Integer(int) - Parameterized constructor for " << m_value << std::endl;
// }
// Integer::Integer(const Integer& other) : m_value(other.m_value) {
// std::cout << "Integer::Integer(const Integer&) - Copy constructor for " << m_value << std::endl;
// }
// Integer::~Integer() {
// std::cout << "Integer::~Integer() - Destructor for " << m_value << std::endl;
// }
// int Integer::getValue() const {
// return m_value;
// }
// void Integer::setValue(int value) {
// m_value = value;
// }
*/

注意:为了简洁和符合常见的短函数内联实践,我们在Integer.h中直接实现了这些方法。因此Integer.cpp文件在这个例子中可以保持为空。

2.2 Point 类:包含Integer成员变量

现在,我们来创建Point类,并让它包含Integer类型的成员变量。我们将演示各种不同类型的成员变量:普通成员变量、常量成员变量、指针成员变量、静态成员变量和静态常量成员变量。

Point.h 文件:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#ifndef POINT_H
#define POINT_H

#include "Integer.h" // 包含Integer类的定义

class Point {
private:
double m_x;
double m_y;
static int s_pointCount; // 静态成员变量,用于记录Point对象数量

// --- Integer类型的成员变量 ---
Integer i1; // 普通Integer对象
const Integer i2; // 常量Integer对象
inline static Integer i3 = 100; // 内联静态Integer对象,可以在类内初始化
inline static const Integer i4 = 200; // 内联静态常量Integer对象,可以在类内初始化
Integer* i5; // 指针Integer对象

public:
// 静态成员变量,需要类外定义和初始化
static Integer i6;
static const Integer i7;

// 构造函数
Point(double x_val, double y_val);

// 析构函数
~Point();

// 获取x值
double getX() const { return m_x; }
// 获取y值
double getY() const { return m_y; }

// 获取Integer成员变量的值
int getI1Value() const { return i1.getValue(); }
int getI2Value() const { return i2.getValue(); }
int getI3Value() const { return i3.getValue(); }
int getI4Value() const { return i4.getValue(); }
int getI5Value() const { return i5 ? i5->getValue() : -1; } // 检查指针是否为空
int getI6Value() const { return i6.getValue(); }
int getI7Value() const { return i7.getValue(); }

// 设置Integer成员变量的值 (注意i2, i4, i7是const,不能设置)
void setI1Value(int val) { i1.setValue(val); }
void setI5Value(int val) { if (i5) i5->setValue(val); } // 检查指针是否为空
void setI6Value(int val) { i6.setValue(val); }

// 静态方法,获取Point对象总数
static int getPointCount();
};

#endif // POINT_H

Point.cpp 文件:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "Point.h"
#include <iostream>

// 初始化静态成员变量
int Point::s_pointCount = 0;
Integer Point::i6 = 600; // 静态Integer成员变量,必须在类外定义和初始化
const Integer Point::i7 = 700; // 静态常量Integer成员变量,必须在类外定义和初始化

// 构造函数实现
// 注意:i2 (const Integer) 和 i5 (Integer*) 必须在成员初始化列表(member initializer list)中初始化
Point::Point(double x_val, double y_val)
: m_x(x_val), m_y(y_val),
i2(20), // i2是const Integer,必须在这里初始化
i5(new Integer(50)) // i5是指针,这里直接分配内存并构造Integer对象
{
s_pointCount++; // 每次创建Point对象,计数器加一
std::cout << "Point::Point() - Constructor for (" << m_x << ", " << m_y << ") with i1=" << i1.getValue()
<< ", i2=" << i2.getValue() << ", i3=" << i3.getValue() << ", i4=" << i4.getValue()
<< ", i5=" << i5->getValue() << ", i6=" << i6.getValue() << ", i7=" << i7.getValue() << std::endl;
}

// 析构函数实现
Point::~Point() {
s_pointCount--; // 每次销毁Point对象,计数器减一
delete i5; // 释放i5指向的内存
i5 = nullptr; // 将指针置空,防止悬空指针(dangling pointer)
std::cout << "Point::~Point() - Destructor for (" << m_x << ", " << m_y << ")" << std::endl;
}

// 静态方法实现
int Point::getPointCount() {
return s_pointCount;
}

2.3 main.cpp 文件:测试和演示

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "Point.h"
#include <iostream>

int main() {
std::cout << "--- 开始程序 ---" << std::endl;

// 创建Point对象
Point p1(10.0, 20.0);

// 访问并打印各种Integer成员变量的值
std::cout << "\n--- 访问Point对象的Integer成员变量 ---" << std::endl;
std::cout << "p1.i1 value: " << p1.getI1Value() << std::endl; // i1: 普通Integer,默认构造函数
std::cout << "p1.i2 value: " << p1.getI2Value() << std::endl; // i2: const Integer,在构造函数初始化列表中初始化
std::cout << "p1.i3 value: " << p1.getI3Value() << std::endl; // i3: inline static Integer,类内初始化
std::cout << "p1.i4 value: " << p1.getI4Value() << std::endl; // i4: inline static const Integer,类内初始化
std::cout << "p1.i5 value: " << p1.getI5Value() << std::endl; // i5: Integer*,在构造函数初始化列表中new分配
std::cout << "p1.i6 value: " << p1.getI6Value() << std::endl; // i6: static Integer,类外初始化
std::cout << "p1.i7 value: " << p1.getI7Value() << std::endl; // i7: static const Integer,类外初始化

// 尝试修改非const Integer成员变量
std::cout << "\n--- 尝试修改Integer成员变量 ---" << std::endl;
p1.setI1Value(111);
std::cout << "p1.i1 new value: " << p1.getI1Value() << std::endl;

p1.setI5Value(555);
std::cout << "p1.i5 new value: " << p1.getI5Value() << std::endl;

p1.setI6Value(666);
std::cout << "p1.i6 new value: " << p1.getI6Value() << std::endl;

// 尝试修改常量成员变量 (会产生编译错误!)
// p1.setI2Value(222); // 编译错误:i2是const
// p1.setI4Value(444); // 编译错误:i4是const
// p1.setI7Value(777); // 编译错误:i7是const

std::cout << "\n--- 创建更多Point对象并观察PointCount ---" << std::endl;
Point p2(30.0, 40.0);
std::cout << "Current Point Count: " << Point::getPointCount() << std::endl; // 通过静态方法访问

{
Point p3(50.0, 60.0);
std::cout << "Current Point Count (inside scope): " << Point::getPointCount() << std::endl;
} // p3 在这里超出作用域并被销毁

std::cout << "Current Point Count (after p3 destroyed): " << Point::getPointCount() << std::endl;


std::cout << "\n--- 程序结束 ---" << std::endl;
return 0; // p1 和 p2 在这里超出作用域并被销毁
}

2.4 核心要点及对比

现在,让我们来逐一分析各种成员变量的声明和初始化方式,以及与上一讲的“自身类型”成员变量的区别。

  1. 普通成员变量 (Integer i1;):

    • 声明:直接在类定义内部声明,就像内置类型一样。
    • 初始化:如果类(Point)没有提供显式的构造函数来初始化它,或者在Point的构造函数初始化列表中没有显式初始化它,它会调用Integer默认构造函数(default constructor)进行初始化。如果Integer类没有默认构造函数,则会引发编译错误。
    • 区别:与上一讲不同,这里没有“不完整类型”(incomplete type)的问题。因为Integer类在Point.h中被#include了,编译器在处理Point类时,Integer的完整定义是可见的。
  2. 常量成员变量 (const Integer i2;):

    • 声明:在类型前加上const关键字。
    • 初始化必须Point类的构造函数的成员初始化列表中进行初始化。这是因为const成员变量在对象构造时必须立即获得其最终值,不能在构造函数体内部赋值(那样是先默认构造再赋值,但const不允许)。
    • 区别:同样,没有“不完整类型”问题。其初始化规则与内置类型的const成员变量类似。
  3. 内联静态成员变量 (inline static Integer i3 = 100;):

    • 声明:使用inline static关键字。
    • 初始化:可以在类定义内部直接进行初始化。inline关键字允许在头文件中定义和初始化静态成员变量,避免了多重定义错误(multiple definition error)。对于用户自定义类型(user-defined type)的静态成员变量,inline是实现类内初始化的关键。
    • 区别:在C++17之前,只有静态常量整型(static const integral type)成员变量(如int, char, bool等)才允许在类内初始化。对于用户自定义类型如Integer,需要使用inline才能在类内初始化。
  4. 内联静态常量成员变量 (inline static const Integer i4 = 200;):

    • 声明:使用inline static const关键字。
    • 初始化:与i3类似,inline关键字使其可以在类定义内部直接初始化。
    • 区别:和i3一样,关键在于inline
  5. 指针成员变量 (Integer* i5;):

    • 声明:声明为一个指针类型。
    • 初始化
      • 可以在类内初始化为nullptr(推荐,避免未初始化指针)。
      • 可以在Point的构造函数成员初始化列表中,使用new关键字来动态分配(dynamically allocate)内存并构造一个Integer对象。
      • 内存管理:由于i5指向的内存是在堆(heap)上动态分配的,因此必须Point类的析构函数delete该内存,以防止内存泄漏(memory leak)。
    • 区别
      • 没有递归调用问题:这是最显著的区别。当Point的构造函数初始化i5时,它调用的是Integer类的构造函数(通过new Integer(50)),而不是Point自身的构造函数。因此,不会像上一讲中自身类型指针那样可能导致无限递归(如果设计不当)。
      • 直接在初始化列表分配内存:因为没有递归问题,我们不需要像上一讲那样通过一个单独的成员函数来间接完成内存分配,可以直接在初始化列表中new
  6. 非内联静态成员变量 (static Integer i6;):

    • 声明:使用static关键字。
    • 初始化必须在类定义外部(通常在.cpp文件中)进行定义和初始化。
    • 特点static成员变量是所有对象共享的,它不属于任何一个具体的Point对象,而是属于Point类本身。它的生命周期与程序的生命周期相同。
  7. 非内联静态常量成员变量 (static const Integer i7;):

    • 声明:使用static const关键字。
    • 初始化:与i6类似,必须在类定义外部(通常在.cpp文件中)进行定义和初始化。
    • 特点:既是static(共享),又是const(常量)。

总结区别
当成员变量是“其他类型”时,最核心的不同在于:

  • 完整类型可见性:因为被包含(#include),编译器在定义包含类时已经知道了成员变量的完整定义,不会有“不完整类型”导致的限制。
  • 构造函数无递归问题:包含类的构造函数调用的是成员变量类型的构造函数,而非自身类的构造函数,因此不会陷入无限递归的陷阱。

2.5 const 正确性(Const Correctness)

在我们的代码中,i2i4i7都被声明为const。这意味着一旦它们被初始化,它们的值就不能再被修改。

  • 尝试调用p1.setI2Value(222)p1.setI4Value(444)p1.setI7Value(777)都将导致编译错误
  • 这是因为const成员变量的setValue方法(如果存在)不能被调用,因为它会尝试修改const数据。即便setValue方法不是const成员函数,只要它尝试修改一个const对象,就会出错。
  • 我们在Integer类中getValue()方法后面加上了const,表示这个方法不会修改对象的状态,因此它可以被const Integer对象调用。这是一个很好的const正确性实践。

4. QA 闪存卡 (QA Flash Cards)

  1. Q1: 在C++中,当一个类拥有“其他类型”的成员变量时,与拥有“自身类型”的成员变量相比,有哪些主要的不同点?

    • A1: 主要的不同点是:
      1. 完整类型可见性:因为“其他类型”通常在类定义之前已被#include,所以不会有“不完整类型”的编译问题。
      2. 构造函数无递归调用问题:包含类的构造函数调用的是成员变量类型的构造函数,而不是自身类型的构造函数,因此不会出现无限递归的风险,可以直接在初始化列表中new分配内存。
  2. Q2: 为什么const Integer i2; 必须在Point类的构造函数的成员初始化列表中进行初始化,而不能在构造函数体内部赋值?

    • A2: 因为i2是一个const(常量)成员变量。const对象在创建时必须立即获得其最终值,不能在构造完成后再赋值。构造函数体内部的语句是在成员变量已经完成初始化之后才执行的,如果在这里赋值,就相当于对一个const对象进行第二次赋值,这是不允许的。成员初始化列表则是在对象构造时,为成员变量分配内存并调用其构造函数(或直接初始化)的阶段。
  3. Q3: inline static Integer i3;static Integer i6; 在初始化方式上有什么不同?

    • A3:
      • inline static Integer i3;:是一个内联静态成员变量。它可以(并且推荐)在类定义内部直接初始化,这得益于C++17引入的inline变量特性,它解决了多重定义问题。
      • static Integer i6;:是一个普通的静态成员变量(非内联)。它必须在类定义外部(通常在.cpp文件中)进行定义和初始化。
  4. Q4: 当一个类拥有Integer* i5; 这样的指针成员变量时,在管理内存方面有什么需要特别注意的?

    • A4: Integer* i5; 这种指针成员变量通常指向在(heap)上通过new动态分配的内存。因此,为了防止内存泄漏(memory leak),你必须在类的析构函数中,使用delete i5; 来释放这块内存。并且为了防止悬空指针(dangling pointer),通常在delete之后将i5赋值为nullptr

5. 常见误解或错误

  1. 忘记在析构函数中释放动态分配的内存:这是最常见的错误之一。如果像Integer* i5; 这样的指针成员变量在构造函数中通过new分配了内存,却忘记在析构函数中delete,就会导致内存泄漏。程序运行时会不断占用内存,最终可能导致系统资源耗尽。
  2. 尝试修改const成员变量:初学者常常会忘记const成员变量一旦初始化后就不能修改的特性,导致编译错误。记住:const就是“常量”,其值固定不变。
  3. 混淆staticinline static的初始化规则:在C++17之前,用户自定义类型的static成员变量无论如何都不能在类内初始化。C++17引入inline变量后,才允许inline static成员变量在类内初始化。如果忘记使用inline而尝试在类内初始化用户自定义类型的static成员变量,会导致编译错误(除非它是静态常量整型)。
  4. static成员变量的误解:认为static成员变量是每个对象都有自己的副本。实际上,static成员变量是类级别的,所有对象共享同一个副本。
  5. 忘记对指针成员变量进行空检查:在访问指针成员变量指向的数据之前(例如i5->getValue()),应该始终检查指针是否为nullptr,以避免空指针解引用(null pointer dereference)导致的程序崩溃。例如if (i5) i5->getValue();

6. 编程练习

现在,轮到你来动手实践了!请在下面的Point类中,添加一个新的Integer类型的成员变量i8,并确保它是一个非内联的静态常量成员变量,其初始值为800。你需要修改Point.hPoint.cpp文件。

你的任务:

  1. Point.h中声明i8
  2. Point.cpp中定义并初始化i8
  3. Point.h中添加一个getI8Value()const成员函数来获取i8的值。
  4. main.cpp中调用getI8Value()并打印其值,验证你的修改。

Point.h (你需要修改这里):

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#ifndef POINT_H
#define POINT_H

#include "Integer.h" // 包含Integer类的定义

class Point {
private:
double m_x;
double m_y;
static int s_pointCount; // 静态成员变量,用于记录Point对象数量

// --- Integer类型的成员变量 ---
Integer i1; // 普通Integer对象
const Integer i2; // 常量Integer对象
inline static Integer i3 = 100; // 内联静态Integer对象,可以在类内初始化
inline static const Integer i4 = 200; // 内联静态常量Integer对象,可以在类内初始化
Integer* i5; // 指针Integer对象

public:
// 静态成员变量,需要类外定义和初始化
static Integer i6;
static const Integer i7;

// TODO: 1. 在这里声明新的非内联静态常量成员变量 i8

// 构造函数
Point(double x_val, double y_val);

// 析构函数
~Point();

// 获取x值
double getX() const { return m_x; }
// 获取y值
double getY() const { return m_y; }

// 获取Integer成员变量的值
int getI1Value() const { return i1.getValue(); }
int getI2Value() const { return i2.getValue(); }
int getI3Value() const { return i3.getValue(); }
int getI4Value() const { return i4.getValue(); }
int getI5Value() const { return i5 ? i5->getValue() : -1; } // 检查指针是否为空
int getI6Value() const { return i6.getValue(); }
int getI7Value() const { return i7.getValue(); }

// TODO: 3. 在这里添加 getI8Value() 的const成员函数

// 设置Integer成员变量的值 (注意i2, i4, i7是const,不能设置)
void setI1Value(int val) { i1.setValue(val); }
void setI5Value(int val) { if (i5) i5->setValue(val); } // 检查指针是否为空
void setI6Value(int val) { i6.setValue(val); }

// 静态方法,获取Point对象总数
static int getPointCount();
};

#endif // POINT_H

Point.cpp (你需要修改这里):

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "Point.h"
#include <iostream>

// 初始化静态成员变量
int Point::s_pointCount = 0;
Integer Point::i6 = 600; // 静态Integer成员变量,必须在类外定义和初始化
const Integer Point::i7 = 700; // 静态常量Integer成员变量,必须在类外定义和初始化

// TODO: 2. 在这里定义并初始化新的非内联静态常量成员变量 i8


// 构造函数实现
// 注意:i2 (const Integer) 和 i5 (Integer*) 必须在成员初始化列表(member initializer list)中初始化
Point::Point(double x_val, double y_val)
: m_x(x_val), m_y(y_val),
i2(20), // i2是const Integer,必须在这里初始化
i5(new Integer(50)) // i5是指针,这里直接分配内存并构造Integer对象
{
s_pointCount++; // 每次创建Point对象,计数器加一
std::cout << "Point::Point() - Constructor for (" << m_x << ", " << m_y << ") with i1=" << i1.getValue()
<< ", i2=" << i2.getValue() << ", i3=" << i3.getValue() << ", i4=" << i4.getValue()
<< ", i5=" << i5->getValue() << ", i6=" << i6.getValue() << ", i7=" << i7.getValue() << std::endl;
}

// 析构函数实现
Point::~Point() {
s_pointCount--; // 每次销毁Point对象,计数器减一
delete i5; // 释放i5指向的内存
i5 = nullptr; // 将指针置空,防止悬空指针(dangling pointer)
std::cout << "Point::~Point() - Destructor for (" << m_x << ", " << m_y << ")" << std::endl;
}

// 静态方法实现
int Point::getPointCount() {
return s_pointCount;
}

main.cpp (你需要修改这里):

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "Point.h"
#include <iostream>

int main() {
std::cout << "--- 开始程序 ---" << std::endl;

// 创建Point对象
Point p1(10.0, 20.0);

// 访问并打印各种Integer成员变量的值
std::cout << "\n--- 访问Point对象的Integer成员变量 ---" << std::endl;
std::cout << "p1.i1 value: " << p1.getI1Value() << std::endl; // i1: 普通Integer,默认构造函数
std::cout << "p1.i2 value: " << p1.getI2Value() << std::endl; // i2: const Integer,在构造函数初始化列表中初始化
std::cout << "p1.i3 value: " << p1.getI3Value() << std::endl; // i3: inline static Integer,类内初始化
std::cout << "p1.i4 value: " << p1.getI4Value() << std::endl; // i4: inline static const Integer,类内初始化
std::cout << "p1.i5 value: " << p1.getI5Value() << std::endl; // i5: Integer*,在构造函数初始化列表中new分配
std::cout << "p1.i6 value: " << p1.getI6Value() << std::endl; // i6: static Integer,类外初始化
std::cout << "p1.i7 value: " << p1.getI7Value() << std::endl; // i7: static const Integer,类外初始化

// TODO: 4. 在这里调用 getI8Value() 并打印其值

// 尝试修改非const Integer成员变量
std::cout << "\n--- 尝试修改Integer成员变量 ---" << std::endl;
p1.setI1Value(111);
std::cout << "p1.i1 new value: " << p1.getI1Value() << std::endl;

p1.setI5Value(555);
std::cout << "p1.i5 new value: " << p1.getI5Value() << std::endl;

p1.setI6Value(666);
std::cout << "p1.i6 new value: " << p1.getI6Value() << std::endl;

std::cout << "\n--- 创建更多Point对象并观察PointCount ---" << std::endl;
Point p2(30.0, 40.0);
std::cout << "Current Point Count: " << Point::getPointCount() << std::endl; // 通过静态方法访问

{
Point p3(50.0, 60.0);
std::cout << "Current Point Count (inside scope): " << Point::getPointCount() << std::endl;
} // p3 在这里超出作用域并被销毁

std::cout << "Current Point Count (after p3 destroyed): " << Point::getPointCount() << std::endl;


std::cout << "\n--- 程序结束 ---" << std::endl;
return 0; // p1 和 p2 在这里超出作用域并被销毁
}

请你完成上述代码修改,然后将你的解决方案提供给我。期待你的答案!


评论